package cn.yx.common.web.handler;

import cn.yx.common.core.constant.MdcConstant;
import cn.yx.common.core.enums.SentinelExceptionEnum;
import cn.yx.common.core.exception.AppException;
import cn.yx.common.core.exception.CommonException;
import cn.yx.common.core.pojo.ResponseVo;
import cn.yx.common.i18n.service.I18nMessageService;
import cn.yx.common.web.util.BindingResultUtil;
import cn.yx.sys.remote.dto.SysI18nMessageRemoteDto;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.converter.HttpMessageConversionException;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import javax.servlet.http.HttpServletRequest;
import java.util.Objects;

/**
 * <p>全局异常处理</p>
 *
 * @author Wgssmart
 */
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {

    @Value("${exception.printStackTrace:true}")
    private boolean printStackTrace;

    private final I18nMessageService i18nMessageService;

    public GlobalExceptionHandler(I18nMessageService i18nMessageService) {
        this.i18nMessageService = i18nMessageService;
    }

    /**
     * Exception类异常处理
     *
     * @param request
     * @param exception
     * @return
     */
    @ExceptionHandler(value = Exception.class)
    public ResponseVo<Void> allExceptionHandler(HttpServletRequest request, Exception exception) {
        log.error("error occur, request url: {}, message: {}", request.getRequestURL(), exception.getMessage());
        printStackTrace(exception);
        if (exception instanceof RuntimeException
                && exception.getCause() instanceof NullPointerException) {
            // TODO 空指针异常，异步推送消息到开发
            String traceId = MDC.get(MdcConstant.TRACE_ID);
            System.out.println(traceId);
        }
        return ResponseVo.fail();
    }

    /**
     * 统一处理HttpMessageConversionException异常，如前端未传递指定格式的参数
     *
     * @param httpMessageConversionException
     * @return
     */
    @ExceptionHandler(HttpMessageConversionException.class)
    public ResponseVo<Void> httpMessageConversionExceptionHandler(HttpServletRequest request,
                                                                  HttpMessageConversionException httpMessageConversionException) {
        log.error("error occur, request url: {}, message: {}", request.getRequestURL(), httpMessageConversionException.getMessage());
        printStackTrace(httpMessageConversionException);
        return ResponseVo.dataError();
    }

    /**
     * 统一处理BindException异常
     *
     * @param bindException
     * @return
     */
    @ExceptionHandler(BindException.class)
    public ResponseVo<Void> validExceptionHandler(BindException bindException) {
        BindingResult bindingResult = bindException.getBindingResult();
        return ResponseVo.dataError(BindingResultUtil.errorString(bindingResult));
    }

    /**
     * 统一处理MethodArgumentNotValidException异常
     *
     * @param methodArgumentNotValidException
     * @return
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseVo<Void> validExceptionHandler(MethodArgumentNotValidException methodArgumentNotValidException) {
        BindingResult bindingResult = methodArgumentNotValidException.getBindingResult();
        return ResponseVo.dataError(BindingResultUtil.errorString(bindingResult));
    }

    /**
     * 处理AppException，在ResponseVo中返回自定义异常消息
     *
     * @param request
     * @param appException
     * @return
     */
    @ExceptionHandler(value = {AppException.class})
    public ResponseVo<Void> appExceptionHandler(HttpServletRequest request, AppException appException) {
        // AppException可带参数，可获取消息，并把参数放入到消息中
        SysI18nMessageRemoteDto sysI18nMessageRemoteDto = getMessage(appException.getMessage(), appException.getMessageParams());
        log.error("error occur, request url: {}, message key: {}, message: {}, message level: {}",
                request.getRequestURL(), sysI18nMessageRemoteDto.getMessageKey(), sysI18nMessageRemoteDto.getMessage(), sysI18nMessageRemoteDto.getLevel());
        printStackTrace(appException);
        // TODO 根据消息level异步推送消息到指定人员
        SentinelExceptionEnum sentinelExceptionEnum = SentinelExceptionEnum.findByValue(appException.getCause().getClass().getSimpleName());
        boolean isSentinelException = Objects.nonNull(sentinelExceptionEnum);
        return isSentinelException ? ResponseVo.serviceError(sysI18nMessageRemoteDto.getMessage())
                : ResponseVo.fail(sysI18nMessageRemoteDto.getMessage());
    }

    /**
     * 处理CommonException，在ResponseVo中不返回具体的异常消息，而是返回失败
     *
     * @param request
     * @param commonException
     * @return
     */
    @ExceptionHandler(value = {CommonException.class})
    public ResponseVo<Void> commonExceptionHandler(HttpServletRequest request, CommonException commonException) {
        // CommonException可带参数，可获取消息，并把参数放入到消息中
        SysI18nMessageRemoteDto sysI18nMessageRemoteDto = getMessage(commonException.getMessage(), commonException.getMessageParams());
        log.error("error occur, request url: {}, message key: {}, message: {}, message level: {}",
                request.getRequestURL(), sysI18nMessageRemoteDto.getMessageKey(), sysI18nMessageRemoteDto.getMessage(), sysI18nMessageRemoteDto.getLevel());
        printStackTrace(commonException);
        // TODO 根据消息level异步推送消息到指定人员
        return ResponseVo.fail(sysI18nMessageRemoteDto.getMessage());
    }

    private void printStackTrace(Exception exception) {
        if (printStackTrace) {
            log.error("stack trace", exception);
        }
    }

    private SysI18nMessageRemoteDto getMessage(String messageKey, String[] messageParams) {
        return i18nMessageService.getSysI18nMessage(messageKey, messageParams);
    }

}